// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: lucasyoo

#pragma once

#include <string>
#include <tuple>

#include <boost/asio.hpp>
#include <fmt/core.h>
#include <glog/logging.h>
#include <google/protobuf/util/json_util.h>
#include <nlohmann/json.hpp>
#include <rapidjson/encodedstream.h>

#include "coin2/base/log.h"
#include "coin2/exchange/base/feed/timer/deadline_timer.h"
#include "coin2/exchange/base/feed/timer/timer.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book.h"
#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/exchange/risk/risk_manager.h"
#include "coin2/strategy/hamm/hamm.pb.h"
#include "coin2/strategy/hamm/hamm_aggressive_controller.h"
#include "coin2/strategy/hamm/hamm_pricer.h"
#include "coin2/strategy/hooks/pnl_monitor.h"
#include "coin2/strategy/order_executor.pb.h"
#include "coin2/strategy/order_executor/layering_executor2.h"
#include "coin2/strategy/order_executor/mm_executor.h"
#include "coin2/strategy/strategy.h"
#include "coin2/strategy/util/reporter2.h"

namespace coin2::strategy::hamm {

using coin::proto::OrderDirection;
using coin::proto::OrderDuration;
using coin::proto::OrderEvent;
using coin::proto::OrderSide;
using coin::proto::OrderState;
using coin::proto::OrderType;

using coin2::app::impl::Driver;

using coin2::exchange::base::feed::FeedTimer;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::feed::timer::DeadlineTimer;
using coin2::exchange::base::order::IOrderSubsystem;
using coin2::exchange::base::order::OrderUpdate;
using coin2::exchange::base::symbology::IProduct;
using coin2::exchange::base::symbology::IProductInfo;
using coin2::exchange::base::symbology::ProductEncyclopedia;
using coin2::exchange::order::RiskManagerConfig;
using coin2::exchange::order::SimConfig;
using coin2::exchange::risk::RiskManager;

using coin2::strategy::PassiveExecutorConfig;
using coin2::strategy::hamm::AggressiveController;
using coin2::strategy::hooks::PnlMonitor;
using coin2::strategy::order_executor::LayeringExecutor2;
using coin2::strategy::order_executor::PassiveOrderExecutor;
using coin2::strategy::util::StrategyReporter2;

using OrderSpec = coin2::exchange::base::order::OrderSpec;

uint64_t GetProductId(const IProduct& product);

class HammStrategy;

//------------------------------------
// HammSubStrategy
//------------------------------------
class HammSubStrategy {
 public:
  HammSubStrategy(
      const std::string& symbol,
      const HammSubStrategyConfig& config,
      HammPricerConfig* pricer_config,
      AggressiveControllerConfig* agg_controller_config,
      const int64_t& strategy_created_time,
      const bool& randomize_order,
      const double& order_update_period_sec,
      const double& max_posting_period_sec,
      const double& fill_cooldown_period_sec);
  void Init(Driver* driver);
  void onBookFeed(const FeedUpdate& upd, const bool& is_agg_on);
  void onAccountInfo(const OrderUpdate& upd);
  void onAccountOrder(const OrderUpdate& upd);
  void onTradeFeed(const FeedUpdate& upd);
  void onOrderLog(const coin::proto::OrderGatewayLog& order_log);
  void onHeartbeatFeed(const FeedTimer& timer);

  void ManageMmOrders(
      const FeedUpdate& upd,
      double ask,
      double bid,
      double ask_qty,
      double bid_qty,
      bool is_cancel_all_ask,
      bool is_cancel_all_bid,
      const bool& is_agg_on);

  // Getter
  IOrderSubsystem* os() const {
    // get os
    CHECK(os_);
    return os_;
  }

  MarketExchangeApi mea() const {
    CHECK(config_.pass_executor_config().has_mea()) << config_.DebugString();
    return MarketExchangeApi::FromString(config_.pass_executor_config().mea());
  }
  double Reserve() const { return config_.pass_executor_config().reserve(); }
  const IProduct* Product() const { return product_.get(); }
  const double& MidP() const { return pricer_->MidP(); }
  const std::pair<double, double>& Bbo() const { return pricer_->Bbo(); }
  const int64_t& ArbOrder() const { return arb_order_; }
  LayeringExecutor2* Executor() { return executor_.get(); }
  const PassiveExecutorConfig& ExecutorConfig() const { return exec_config_; }
  const int64_t& StrategyCreatedTime() const { return strategy_created_time_; }
  HammPricer* Pricer() { return pricer_.get(); }
  const bool& DryRun() const { return dry_run_; }
  std::pair<double, double> GenerateBandwidthBp();

  // Setter
  void SetRiskManager(RiskManager* risk_manager) { risk_manager_ = risk_manager; }
  void SetArbFreeBbo(std::pair<double, double> bbo, std::pair<bool, bool> is_adjust);
  void ResetArbFreeBbo();

 private:
  HammSubStrategyConfig config_;
  AggressiveControllerConfig* agg_controller_config_;
  RiskManager* risk_manager_;
  Driver* driver_;
  ProductEncyclopedia product_cache_;
  const IProductInfo* pi_;
  IOrderSubsystem* os_{nullptr};
  std::unique_ptr<IProduct> product_;
  std::unique_ptr<HammPricer> pricer_{nullptr};
  std::unique_ptr<LayeringExecutor2> executor_{nullptr};
  std::unique_ptr<AggressiveController> agg_controller_{nullptr};
  PassiveExecutorConfig exec_config_;
  int64_t strategy_created_time_;
  bool is_trading_futures_;
  bool dry_run_;
  int64_t timestamp_;
  double tolerance_bps_;
  int64_t arb_order_;
  bool randomize_order_{true};
  bool is_aggressive_;
  double max_buy_price_{0};
  double min_sell_price_{0};
  double min_bandwidth_bp_;
  double max_bandwidth_bp_;
  int layering_num_;
  double latest_order_info_[4]{0, 0, 0, 0};
  int64_t order_manager_log_ts_{0};
  std::optional<double> lot_size_quote_;
};

//------------------------------------
// HammStrategy
//------------------------------------
class HammStrategy : public coin2::strategy::IStrategy {
 public:
  HammStrategy(const std::string& config_file, std::optional<int64_t> ts);
  void Init(Driver* driver) final;
  void onBookFeed(const FeedUpdate& upd) final;
  void onAccountInfo(const OrderUpdate& upd) final;
  void onAccountOrder(const OrderUpdate& upd) final;
  void onTradeFeed(const FeedUpdate& upd) final;
  void onHeartbeatFeed(const FeedTimer& timer) final;
  void onOrderLog(const coin::proto::OrderGatewayLog& order_log) final;
  void onOrderSubsystemError(const OrderUpdate& upd) final;
  void onCleanup(Driver* driver) final;
  void onCleanupAux(Driver* driver, int);
  void UpdateSimConfig(std::string name, SimConfig* config);
  void ArbitrageCheck(HammSubStrategy* strat1, HammSubStrategy* strat2, int64_t ts);

 private:
  HammStrategyConfig config_;
  Driver* driver_;
  std::unique_ptr<StrategyReporter2> strat_reporter_{nullptr};
  std::unique_ptr<RiskManager> risk_manager_{nullptr};
  std::unordered_map<uint64_t, std::unique_ptr<HammSubStrategy>> strats_;
  std::map<int64_t, HammSubStrategy*> ordered_strats_;
  std::unique_ptr<PnlMonitor> pnl_monitor_{nullptr};
  std::unordered_map<const IProduct*, double> reserve_map_;
  std::unique_ptr<DeadlineTimer> feed_timer_{nullptr};

  int64_t strategy_created_time_;
  double arb_tolerance_bps_;

  std::unordered_map<uint64_t, double> bid_map_;
  std::unordered_map<uint64_t, bool> agg_on_off_map_;
};

}  // namespace coin2::strategy::hamm
